#ifndef __SVD_DATA_H__
#define __SVD_DATA_H__
#include <vector>
#include <cstring>
#include "type_def.h"

struct ReData {
	struct Sample {
		float label;
		int num_global;
		int num_ufactor;
		int num_ifactor;
		unsigned *index_global;
		unsigned *index_ufactor;
		unsigned *index_ifactor;
		float *value_global;
		float *value_ufactor;
		float *value_ifactor;

		inline int total_num(void) const {
			return num_global + num_ufactor + num_ifactor;
		}

		inline void set_space(unsigned *index, float *value) {
			index_global = index;
			value_global = value;
			index_ufactor = index_global + num_global;
			index_ifactor = index_ufactor + num_ufactor;
			value_ufactor = value_global + num_global;
			value_ifactor = value_ufactor + num_ufactor;
		}

		inline void alloc_space(void) {
			this->set_space(new unsigned[total_num()], new float[total_num()]);
		}

		inline void free_space(void) {
			delete[] index_global;
			delete[] value_global;
		}

		inline Sample clone(void) const {
			Sample val = *this;
			val.alloc_space();
			memcpy(val.index_global, index_global, sizeof(unsigned) * num_global);
			memcpy(val.value_global, value_global, sizeof(float) * num_global);
			memcpy(val.index_ufactor, index_ufactor, sizeof(unsigned) * num_ufactor);
			memcpy(val.value_ufactor, value_ufactor, sizeof(float) * num_ufactor);
			memcpy(val.index_ifactor, index_ifactor, sizeof(unsigned) * num_ifactor);
			memcpy(val.value_ifactor, value_ifactor, sizeof(float) * num_ifactor);
			return val;
		}
	};
	int num_row;
	int num_val;
	float *row_label;
	int *row_ptr;
	unsigned *feat_index;
	float *feat_value;
	inline Sample operator[](int r) {
		Sample e;
		e.label = row_label[r];
		e.num_global = row_ptr[r * 3 + 1] - row_ptr[r * 3 + 0];
		e.num_ufactor = row_ptr[r * 3 + 2] - row_ptr[r * 3 + 1];
		e.num_ifactor = row_ptr[r * 3 + 3] - row_ptr[r * 3 + 2];
		e.index_global = feat_index + row_ptr[r * 3 + 0];
		e.index_ufactor = feat_index + row_ptr[r * 3 + 1];
		e.index_ifactor = feat_index + row_ptr[r * 3 + 2];
		e.value_global = feat_value + row_ptr[r * 3 + 0];
		e.value_ufactor = feat_value + row_ptr[r * 3 + 1];
		e.value_ifactor = feat_value + row_ptr[r * 3 + 2];
		return e;
	}
	inline const Sample operator[](int r) const {
		Sample e;
		e.label = row_label[r];
		e.num_global = row_ptr[r * 3 + 1] - row_ptr[r * 3 + 0];
		e.num_ufactor = row_ptr[r * 3 + 2] - row_ptr[r * 3 + 1];
		e.num_ifactor = row_ptr[r * 3 + 3] - row_ptr[r * 3 + 2];
		e.index_global = feat_index + row_ptr[r * 3 + 0];
		e.index_ufactor = feat_index + row_ptr[r * 3 + 1];
		e.index_ifactor = feat_index + row_ptr[r * 3 + 2];
		e.value_global = feat_value + row_ptr[r * 3 + 0];
		e.value_ufactor = feat_value + row_ptr[r * 3 + 1];
		e.value_ifactor = feat_value + row_ptr[r * 3 + 2];
		return e;
	}
	inline ReData slice_rows(int r_start, int num_row) const {
		ReData sp;
		sp.num_row = num_row;
		sp.num_val = row_ptr[r_start * 3 + num_row * 3] - row_ptr[r_start * 3];
		sp.row_label = this->row_label + r_start;
		sp.row_ptr = this->row_ptr + r_start * 3;
		sp.feat_index = this->feat_index;
		sp.feat_value = this->feat_value;
		return sp;
	}
	inline void alloc_space(void) {
		row_ptr = new int[num_row * 3 + 1];
		row_label = new float[num_row];
		feat_index = new unsigned[num_val];
		feat_value = new float[num_val];
	}
	inline void free_space(void) {
		delete[] row_ptr;
		delete[] row_label;
		delete[] feat_index;
		delete[] feat_value;
	}
	inline void save_to_file(FILE *fo) const {
		const int start = row_ptr[0];
		std::vector<int> tmp_ptr;
		for (int i = 0; i < num_row * 3 + 1; i++)
			tmp_ptr.push_back(row_ptr[i] - start);

		fwrite(this, sizeof(int), 2, fo);
		fwrite(&tmp_ptr[0], sizeof(int), num_row * 3 + 1, fo);
		fwrite(row_label, sizeof(float), num_row, fo);
		if (num_val > 0) {
			fwrite(feat_index + start, sizeof(unsigned), num_val, fo);
			fwrite(feat_value + start, sizeof(float), num_val, fo);
		}
	}
	inline void load_from_file(FILE *fi) {
		assert(fread(this, sizeof(int), 2, fi) > 0);
		assert(fread(row_ptr, sizeof(int), num_row * 3 + 1, fi) > 0);
		assert(fread(row_label, sizeof(float), num_row, fi) > 0);
		if (num_val > 0) {
			assert(fread(feat_index, sizeof(unsigned), num_val, fi) > 0);
			assert(fread(feat_value, sizeof(float), num_val, fi) > 0);
		}
	}
};

struct ReDataBlock {
	enum {
		DEFAULT = 0, START_TAG, END_TAG, MIDDLE_TAG
	};
	int num_ufeedback;
	int extend_tag;
	unsigned *index_ufeedback;
	float *value_ufeedback;
	ReData data;
	ReDataBlock() {
		extend_tag = DEFAULT;
	}
	inline void alloc_space() {
		index_ufeedback = new unsigned[num_ufeedback];
		value_ufeedback = new float[num_ufeedback];
		data.alloc_space();
	}

	inline void free_space(void) {
		data.free_space();
		delete[] index_ufeedback;
		delete[] value_ufeedback;
	}

	inline void save_to_file(FILE *fo) const {
		// store extend_tag if it's not default, use negative num_ufeedback to mark
		if (extend_tag != DEFAULT) {
			int nu = num_ufeedback | (1 << 31);
			fwrite(&nu, sizeof(int), 1, fo);
			fwrite(&extend_tag, sizeof(int), 1, fo);
		} else {
			fwrite(&num_ufeedback, sizeof(int), 1, fo);
		}
		fwrite(index_ufeedback, sizeof(unsigned), num_ufeedback, fo);
		fwrite(value_ufeedback, sizeof(float), num_ufeedback, fo);
		data.save_to_file(fo);
	}

	inline void load_from_file(FILE *fi) {
		assert(fread(&num_ufeedback, sizeof(int), 1, fi) > 0);
		// use negative num_ufeedback to mark non-default extend_tag, load if necessary
		if (num_ufeedback < 0) {
			num_ufeedback = num_ufeedback & (~(1 << 31));
			assert(fread(&extend_tag, sizeof(int), 1, fi) > 0);
		} else {
			this->extend_tag = DEFAULT;
		}
		if (num_ufeedback > 0) {
			assert(fread(index_ufeedback, sizeof(unsigned), num_ufeedback, fi) > 0);
			assert(fread(value_ufeedback, sizeof(float), num_ufeedback, fi) > 0);
		}
		data.load_from_file(fi);
	}
	inline ReDataBlock clone(void) const {
		ReDataBlock val = *this;
		val.alloc_space();
		memcpy(val.index_ufeedback, index_ufeedback, sizeof(unsigned) * num_ufeedback);
		memcpy(val.value_ufeedback, value_ufeedback, sizeof(float) * num_ufeedback);
		memcpy(val.data.row_ptr, data.row_ptr, sizeof(int) * (data.num_row * 3 + 1));
		memcpy(val.data.row_label, data.row_label, sizeof(float) * data.num_row);
		memcpy(val.data.feat_index, data.feat_index, sizeof(unsigned) * data.num_val);
		memcpy(val.data.feat_value, data.feat_value, sizeof(float) * data.num_val);
		return val;
	}
};
template<typename DType>
class IDataIterator {
public:
	virtual void set_param(const char *name, const char *val) = 0;
	virtual void init(void) = 0;
	virtual void before_first(void) = 0;
	virtual bool next(DType &e) = 0;
	virtual size_t get_data_size(void) {
		return 0;
	}
	virtual ~IDataIterator(void) {
	}
};

const int BINARY_BUFFER = 0;
const int TEXT_FEATURE = 1;
const int BINARY_BUFFER_RANK = 2;
const int TEXT_FEATURE_RANK = 3;

IDataIterator<ReDataBlock> *create_plus_iterator(int dtype);
IDataIterator<ReData::Sample> *create_slavethread_iter(IDataIterator<ReData::Sample> *slave_iter);
IDataIterator<ReDataBlock> *create_slavethread_iter(IDataIterator<ReDataBlock> *slave_iter);

void create_binary_buffer(const char *name_buf, IDataIterator<ReData::Sample> *data_iter, int batch_size = 1000);
void create_binary_buffer(const char *name_buf, IDataIterator<ReDataBlock> *data_iter);

#endif		// __SVD_DATA_H__
